package net.coding.api.test.isolation;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.Files;

import net.coding.api.test.JedisClient;
import net.coding.api.test.TestClient;
import net.coding.api.test.enums.ProjectTypeEnum;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.junit.Assert;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import io.restassured.RestAssured;
import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import static net.coding.api.test.TestClient.CONFIG;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.hamcrest.Matchers.not;

@Data
@Builder
@Slf4j
public class Account {
    private final static String ISO = "CN";


    private String domain;
    private String enterpriseName;
    private String uuid;
    private String email;
    private String username;
    private String password;
    private String name;
    private String phone;
    private String countryCode;
    private String cookie;
    private String projectName = RandomStringUtils.randomAlphabetic(8);
    private String httpsURL;
    private String sshURL;
    private RequestSpecification given;
    private File gitSpace;
    private Integer projectId;
    private Integer teamId;
    private String token;

    private RequestSpecification given() {

        String baseURI = CONFIG.getProperty("baseURI");

        String newURI = StringUtils.replacePattern(
                baseURI,
                "\\/\\/(?:[a-z0-9A-Z_-]+)",
                "//" + domain
        );

        return RestAssured.given()
                .baseUri(newURI)
                .basePath("/");
    }


    private Account login(String account, String password) {
        Response response = given().when()
                .param("account", account)
                .param("password", DigestUtils.shaHex(password))
                .post("/api/v2/account/login");

        cookie = response.getCookie(TestClient.getIdentificationName());

        JsonPath jsonPath = response.jsonPath();

        response.then()
                .assertThat()
                .body("code", equalTo(0));

        name = jsonPath.getString("data.name");
        phone = jsonPath.getString("data.phone");
        countryCode = jsonPath.getString("data.phone_country_code");

        return this;
    }

    public Account loginWithEmail() {
        return login(email, password);
    }

    public Account loginWithUsername() {
        return login(username, password);
    }

    public Account loginWithPhone() {
        return login(phone, password);
    }

    public Account checkName(boolean success) {
        Response response = given().when().get("/api/user/check/" + name);

        if (success) {
            response.then().assertThat()
                    .body("data", equalTo(false));
        } else {
            response.then().assertThat()
                    .body("data", equalTo(true));
        }
        return this;
    }

    public Account checkEmail(boolean success) {
        Response response = given().when()
                .param("key", email)
                .get("/api/user/check");

        if (success) {
            response.then().assertThat()
                    .body("data", equalTo(true));
        } else {
            response.then().assertThat()
                    .body("data", equalTo(false));
        }
        return this;
    }

    public Account checkPhone(boolean success) {
        Response response = given().when()
                .param("phone", phone)
                .get("/api/user/check/phone");

        if (success) {
            response.then().assertThat()
                    .body("data", equalTo(true));

            Response sendResponse = given().when()
                    .param("token", token)
                    .param("phone", phone)
                    .post("/api/enterprise/user/generate_phone_code");

            if (StringUtils.isNotBlank(token)) {
                sendResponse.then().assertThat()
                        .body("code", equalTo(0));
            } else {
                sendResponse.then().assertThat()
                        .body("code", not(0));
            }
        } else {
            response.then().assertThat()
                    .body("data", equalTo(false));
        }
        return this;
    }

    public Account checkGK() {
        given().when()
                .param("uuid", uuid)
                .get("/api/enterprise/user-gk/" + username + "/available")
                .then().assertThat()
                .body("data", equalTo(true));

        return this;
    }

    public Account activeUser(boolean success) {
        Response response = given().when()
                .param("token", token)
                .param("name", name)
                .param("teamId", teamId)
                .param("email", email)
                .param("globalKey", username)
                .param("phone", phone)
                .param("code", 111111)
                .param("password", DigestUtils.sha1Hex(password))
                .param("confirm", DigestUtils.sha1Hex(password))
                .param("uuid", uuid)
                .post("/api/enterprise/user/activate");

        if (success) {
            response.then().assertThat()
                    .body("data.email", equalToIgnoringCase(email))
                    .body("data.phone", equalTo(phone))
                    .body("data.name", equalTo(name))
                    .body("data.global_key", equalTo(username));
        } else {
            response.then().assertThat()
                    .body("code", not(0));
        }
        return this;
    }

    public String invite(String email) {
        given().when()
                .param("email", email)
                .cookie(TestClient.getIdentificationName(), cookie)
                .post("/api/enterprise/user/invite")
                .then().assertThat()
                .body("code", equalTo(0));

        Response response = given().when()
                .cookie(TestClient.getIdentificationName(), cookie)
                .get("/api/enterprise/user/invites");


        response.then().assertThat()
                .body("code", equalTo(0));

        List<Map<String, Object>> list = response.jsonPath().getList("data");

        String token = (String) Optional.ofNullable(list)
                .orElse(Lists.newArrayList())
                .stream()
                .filter(m -> StringUtils.equals(email, String.valueOf(m.get("email"))))
                .findFirst()
                .orElse(Maps.newHashMap())
                .get("token");

        return token;
    }


    public Account updateName(String sameName, boolean success) {
        Response response = given().when().
                cookie(TestClient.getIdentificationName(), cookie)
                .param("name", sameName)
                .param("sex", 0)
                .post("/api/user/updateInfo");

        if (success) {
            response.then()
                    .assertThat()
                    .body("data.name", equalTo(sameName));

        } else {
            response.then().assertThat()
                    .body("code", equalTo(1));
        }
        return this;
    }


    public Account resetName() {
        given().when()
                .param("name", name)
                .param("sex", 0)
                .cookie(TestClient.getIdentificationName(), cookie)
                .post("/api/user/updateInfo")
                .then().assertThat()
                .body("data.name", equalTo(name));
        return this;
    }


    public void updatePhone(String phone, boolean success) {
        // 虚拟运营商手机号码段

        Response caResponse = given()
                .cookie(TestClient.getIdentificationName(), cookie)
                .get("/api/getCaptcha");

        caResponse.then()
                .assertThat()
                .header("Content-type", equalTo("image/jpeg; charset=UTF-8"));

        String j_captcha = JedisClient.getValue(cookie + ":DEFAULT_LIMIT");

        Response codeRepsonse = given()
                .param("phone", phone)
                .param("country", ISO)
                .param("phoneCountryCode", countryCode)
                .param("j_captcha", j_captcha)
                .cookie(TestClient.getIdentificationName(), cookie)
                .post("/api/account/phone/change/code");

        if (success) {
            codeRepsonse.then().assertThat()
                    .body("code", equalTo(0));
            given()
                    .param("phone", phone)
                    .param("code", "111111")
                    .param("j_captcha", j_captcha)
                    .param("phoneCountryCode", countryCode)
                    .param("password", DigestUtils.sha1Hex(password))
                    .cookie(TestClient.getIdentificationName(), cookie)
                    .post("/api/account/phone/change")
                    .then()
                    .assertThat()
                    .body("data", equalTo(true));

            // 验证是否更新成功
            given().cookie(TestClient.getIdentificationName(), cookie)
                    .get("/api/current_user")
                    .then()
                    .assertThat()
                    .body("data.phone", equalTo(phone))
                    .body("data.phone_country_code", equalTo(countryCode));
        } else {
            codeRepsonse.then().assertThat()
                    .body("code", equalTo(1026));
        }
    }


    public Account resetPhone() {
        updatePhone(phone, true);
        return this;
    }

    public Account assertEnterprise() {
        given().when().
                cookie(TestClient.getIdentificationName(), cookie).
                get("/api/enterprise/info/" + domain).
                then().
                assertThat().
                body("data.globalKey", equalTo(domain));

        Response response = given().when().
                cookie(TestClient.getIdentificationName(), cookie).
                get("/api/team/" + domain + "/get");
        response.then()
                .assertThat()
                .body("data.global_key", equalTo(domain))
                .body("data.owner.global_key", equalTo(username));

        teamId = response.jsonPath().getInt("data.id");

        return this;
    }

    public Account createProject() {

        if (getProject() != null) {
            deleteProject();
        }

        projectName = RandomStringUtils.randomAlphabetic(8);
        String desc = RandomStringUtils.randomAlphabetic(8);

        given().when().
                param("type", ProjectTypeEnum.PRIVATE.getType()).
                param("name", projectName).
                param("description", desc).
                param("gitEnabled", true).
                param("gitReadmeEnabled", true).
                param("gitIgnore", "no").
                param("gitLicense", "no").
                param("vcsType", "git").
                cookie(TestClient.getIdentificationName(), cookie).
                post("/api/team/" + domain + "/project").
                then().
                assertThat().
                body("data", equalTo("/p/" + projectName));

        getProject();

        return this;
    }

    public Integer getProject() {
        Response response = given().
                when().
                cookie(TestClient.getIdentificationName(), cookie).
                get("/api/user/" + domain + "/project/" + projectName);

        JsonPath jsonPath = response.
                body().
                jsonPath();

        if (jsonPath.getMap("data") == null) {
            return null;
        }

        projectId = jsonPath.getInt("data.id");
        httpsURL = jsonPath.getString("data.https_url");
        sshURL = jsonPath.getString("data.ssh_url");

        return projectId;
    }

    public Account deleteProject() {
        given().when().
                cookie(TestClient.getIdentificationName(), cookie).
                param("two_factor_code", DigestUtils.sha1Hex(password)).
                post("/api/team/" + domain + "/project/" + projectId + "/delete");
        return this;
    }

    public Account deleteMember(String username) {
        given().when().
                cookie(TestClient.getIdentificationName(), cookie).
                delete("/api/team/normal/member/" + username + "?two_factor_code=" + DigestUtils.sha1Hex(password)).
                then().assertThat().
                body("code", equalTo(0));
        return this;
    }

    public Account clone() {
        gitSpace = Files.createTempDir();
        try {
            Git.cloneRepository()
                    .setURI(httpsURL)
                    .setDirectory(gitSpace)
                    .setCredentialsProvider(credentialsProvider())
                    .call();
        } catch (GitAPIException e) {
            log.error("git clone error", e);
            deleteProject();
        }

        Assert.assertTrue(dotGitDir().exists());
        Assert.assertTrue(readmeFile().exists());
        return this;
    }

    public Account commit() {
        try {
            FileUtils.write(
                    readmeFile(),
                    username + email,
                    Charsets.UTF_8
            );

            Git git = Git.open(gitSpace);
            git.add().addFilepattern(".").call();
            CommitCommand commitCommand = git.commit();
            commitCommand.setMessage("Update README");
            commitCommand.call();
            git.push()
                    .setCredentialsProvider(credentialsProvider())
                    .call();
        } catch (IOException | GitAPIException e) {
            log.error("git update readme", e);
            deleteProject();
        }
        return this;
    }

    public Account cloneAgain() {
        FileUtils.deleteQuietly(gitSpace);
        clone();
        String content = EMPTY;
        try {
            content = FileUtils.readFileToString(
                    readmeFile(),
                    Charsets.UTF_8
            );
        } catch (IOException e) {
            log.error("git clone again error", e);
            deleteProject();
        }
        Assert.assertThat(content, equalTo(username + email));
        return this;
    }

    public void clean() {
        deleteProject();
        FileUtils.deleteQuietly(gitSpace);
    }

    private CredentialsProvider credentialsProvider() {
        return new UsernamePasswordCredentialsProvider(
                email,
                password
        );
    }

    private File dotGitDir() {
        return new File(gitSpace.getPath() + "/.git");
    }

    private File readmeFile() {
        return new File(gitSpace.getPath() + "/README.md");
    }
}
